]> dgit.raspbian.org Git - ostree.git/commitdiff
sysroot: Merge bootconfig-extra from previously staged deployment
authorJoseph Marrero Corchado <jmarrero@redhat.com>
Sat, 9 May 2026 19:18:07 +0000 (15:18 -0400)
committerJoseph Marrero Corchado <jmarrero@redhat.com>
Fri, 26 Jun 2026 02:11:28 +0000 (22:11 -0400)
When a staged deployment is replaced by a new one (e.g. bootc sets
source-tracked kargs, then rpm-ostree appends a karg before reboot),
the bootconfig-extra keys from the first staging were lost. The new
staging only checked the new deployment's bootconfig (always empty)
and fell back to the merge deployment's bootconfig (the booted BLS
file), which had no knowledge of what was in the previous staged
GVariant.

Fix this by merging bootconfig-extra from three sources in priority
order:

  1. Merge deployment's bootconfig (lowest priority)
  2. Previously staged deployment's bootconfig-extra
  3. New deployment's bootconfig (highest priority)

This ensures that x-options-source-* keys set by one consumer survive
re-staging by another consumer on the same boot.

Assisted-by: OpenCode (Claude Opus 4.6)
Signed-off-by: Joseph Marrero Corchado <jmarrero@redhat.com>
src/libostree/ostree-sysroot-deploy.c

index 77b601ff42275da3750c7a7f46b785fc5c1630d4..add835fb9b04a3cd073c68bab0a445bfe349fdc7 100644 (file)
@@ -3795,14 +3795,25 @@ _ostree_sysroot_ensure_finalize_staged_service (GError **error)
   return TRUE;
 }
 
+/* Merge all entries from an a{ss} GVariant into a string→string hash table.
+ * Used to accumulate bootconfig-extra keys from multiple sources during staging. */
+static void
+merge_extra_variant_into_table (GVariant *extra, GHashTable *table)
+{
+  GVariantIter iter;
+  const char *k, *v;
+  g_variant_iter_init (&iter, extra);
+  while (g_variant_iter_next (&iter, "{&s&s}", &k, &v))
+    g_hash_table_insert (table, g_strdup (k), g_strdup (v));
+}
+
 /**
  * ostree_sysroot_stage_tree_with_options:
  * @self: Sysroot
- * @osname: (allow-none): osname to use for merge deployment
+ * @osname: osname to use for merge deployment
  * @revision: Checksum to add
  * @origin: (allow-none): Origin to use for upgrades
- * @merge_deployment: (allow-none): Use this deployment for merge path
- * @opts: Options
+ * @opts: (nullable): Options
  * @out_new_deployment: (out): The new deployment path
  * @cancellable: Cancellable
  * @error: Error
@@ -3888,27 +3899,72 @@ ostree_sysroot_stage_tree_with_options (OstreeSysroot *self, const char *osname,
    * These are custom keys set by consumers like bootc and need to survive
    * the staging roundtrip so they are preserved during finalization at shutdown.
    *
-   * First check the new deployment's bootconfig (in case the caller set keys
-   * on it directly).  If none found, fall back to the merge deployment's
-   * bootconfig, which carries the keys from the currently deployed BLS entry.
-   * This ensures that x-prefixed keys are inherited across staged deployments
-   * even though _ostree_deployment_set_bootconfig_from_kargs() creates a fresh
-   * bootconfig containing only the "options" key.
+   * Extension keys can come from three sources, merged in increasing
+   * priority order (higher-priority sources override lower ones for
+   * the same key):
+   *
+   *   1. The merge deployment's bootconfig (on-disk BLS from the
+   *      currently booted or pending deployment) — lowest priority
+   *   2. The previously staged deployment's bootconfig-extra (a prior
+   *      consumer like bootc may have staged keys that would be lost
+   *      when this new staging replaces the old staged GVariant)
+   *   3. The new deployment's bootconfig (caller set keys directly)
+   *      — highest priority
+   *
+   * This merge ensures that e.g. `bootc loader-entries set-options-for-source`
+   * followed by `rpm-ostree kargs --append` on the same boot preserves the
+   * source keys that bootc wrote into the first staged deployment.
    */
   {
-    GVariant *extra = NULL;
-    OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment);
-    if (bootconfig)
-      extra = _ostree_bootconfig_parser_get_extra_keys_variant (bootconfig);
-    if (!extra && merge_deployment)
+    g_autoptr (GHashTable) merged_extra
+        = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+    /* Priority 1 (lowest): merge deployment's bootconfig */
+    if (merge_deployment)
       {
         OstreeBootconfigParser *merge_bootconfig
             = ostree_deployment_get_bootconfig (merge_deployment);
         if (merge_bootconfig)
-          extra = _ostree_bootconfig_parser_get_extra_keys_variant (merge_bootconfig);
+          {
+            g_autoptr (GVariant) merge_extra
+                = _ostree_bootconfig_parser_get_extra_keys_variant (merge_bootconfig);
+            if (merge_extra)
+              merge_extra_variant_into_table (merge_extra, merged_extra);
+          }
+      }
+
+    /* Priority 2: previously staged deployment's bootconfig-extra.
+     * The staged deployment data is already loaded and cached in the
+     * OstreeSysroot during ostree_sysroot_load(). */
+    if (self->staged_deployment_data)
+      {
+        g_autoptr (GVariant) prev_extra = g_variant_lookup_value (
+            self->staged_deployment_data, "bootconfig-extra", (GVariantType *)"a{ss}");
+        if (prev_extra)
+          merge_extra_variant_into_table (prev_extra, merged_extra);
+      }
+
+    /* Priority 3 (highest): new deployment's bootconfig */
+    {
+      OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment);
+      if (bootconfig)
+        {
+          g_autoptr (GVariant) new_extra
+              = _ostree_bootconfig_parser_get_extra_keys_variant (bootconfig);
+          if (new_extra)
+            merge_extra_variant_into_table (new_extra, merged_extra);
+        }
+    }
+
+    if (g_hash_table_size (merged_extra) > 0)
+      {
+        g_auto (GVariantBuilder) extra_builder = OT_VARIANT_BUILDER_INITIALIZER;
+        g_variant_builder_init (&extra_builder, (GVariantType *)"a{ss}");
+        GLNX_HASH_TABLE_FOREACH_KV (merged_extra, const char *, k, const char *, v)
+          g_variant_builder_add (&extra_builder, "{ss}", k, v);
+        g_variant_builder_add (builder, "{sv}", "bootconfig-extra",
+                               g_variant_builder_end (&extra_builder));
       }
-    if (extra)
-      g_variant_builder_add (builder, "{sv}", "bootconfig-extra", extra);
   }
 
   const char *parent = dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED));